package com.wardrobe.smartWardrobe.service.impl;

import cn.hutool.core.util.StrUtil;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.wardrobe.smartWardrobe.common.R;
import com.wardrobe.smartWardrobe.entity.Media;
import com.wardrobe.smartWardrobe.exception.WardrobeException;
import com.wardrobe.smartWardrobe.mapper.MediaMapper;
import com.wardrobe.smartWardrobe.service.MediaService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wardrobe.smartWardrobe.utils.Mp4VideoUtil;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author tky
 * @since 2024-03-24
 */
@Slf4j
@Service
public class MediaServiceImpl extends ServiceImpl<MediaMapper, Media> implements MediaService {
    @Autowired
    MinioClient minioClient;

    @Autowired
    MediaMapper mediaMapper;

    @Autowired
    MediaService mediaService;

    @Value("${minio.bucket.files}")
    private String bucket_Files;

    @Value("${minio.bucket.videofiles}")
    private String bucket_video;

    @Value("${minio.ffmpegpath}")
    String ffmpegpath;

    public Media uploadFile(Media media, String localFilePath,String objectName) {
        String filename = media.getMname();
        File file = new File(localFilePath);
        if (!file.exists()) {
            WardrobeException.cast("文件不存在");
        }
        String fileMd5 = getFileMd5(file);
        //获取ObjectName
        String bucket=bucket_Files;
        String defaultFolderPath = getDefaultFolderPath();
        String[] split = filename.split("\\.");
        String extension = "."+split[split.length - 1];
        if (StrUtil.isEmpty(objectName)) objectName=defaultFolderPath+fileMd5+extension;
        if (".docx".equals(extension))extension=".doc";
        //获取文件后缀名
        String mimeType = getMimeType(extension);
        //保存到Minio
        savaToMinio(bucket, localFilePath, objectName, mimeType);
        //保存文件到数据库
        Media mediaFiles = mediaService.saveToDb(fileMd5, media, bucket, objectName,mimeType);
        //返回数据
        return mediaFiles;
    }


    public R<Boolean> checkFile(String fileMd5){
        Media mediaFiles = mediaMapper.selectById(fileMd5);
        if (mediaFiles!=null){
            String bucket = mediaFiles.getMbucket();
            String url = mediaFiles.getMurl();
            String filePath = url.substring(bucket.length() + 2);
            GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                    .bucket(bucket)
                    .object(filePath)
                    .build();
            try {
                GetObjectResponse object = minioClient.getObject(getObjectArgs);
                if (object!=null) return R.success(true);
                else return R.error(false,"文件信息存在，但文件不存在");

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return R.error(false,"文件信息不存在");
    }

    @Override
    public R<Boolean> checkChunk(String fileMd5, int chunkIndex) {
        String filePath = getFileRootPath(fileMd5)+chunkIndex;
        GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                .bucket(bucket_video)
                .object(filePath)
                .build();
        try {
            GetObjectResponse object = minioClient.getObject(getObjectArgs);
            if (object!=null) return R.success(true);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return R.error(false,"分块不存在");
    }

    @Override
    public R<Boolean> uploadChunk(String fileMd5, int chunk, String localChunkFilePath) {
        String mimeType = getMimeType(null);
        String objectName = getFileRootPath(fileMd5)+chunk;
        boolean b = savaToMinio(bucket_video, localChunkFilePath, objectName, mimeType);
        if (b) return R.success(true);
        else return R.error(false,"分块文件上传失败");
    }

    @Override
    public R<Boolean> mergechunks(String fileMd5, int chunkTotal, Media media) {
        //合并文件
        String fileRootPath = getFileRootPath(fileMd5);
        String filename = media.getMname();
        String extension=filename.substring(filename.lastIndexOf("."));
        String objectName = getFilePathByMd5(fileMd5, extension);
        ArrayList<ComposeSource> sources = new ArrayList<>();
        for (int i = 0; i < chunkTotal; i++) {
            ComposeSource source = ComposeSource.builder()
                    .bucket(bucket_video)
                    .object(fileRootPath + i)
                    .build();
            sources.add(source);
        }
        ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder()
                .bucket(bucket_video)
                .sources(sources)
                .object(objectName)
                .build();
        try {
            minioClient.composeObject(composeObjectArgs);
        } catch (Exception e) {
            log.debug("合并文件失败,fileMd5:{},异常:{}",fileMd5,e.getMessage(),e);
            return R.error(false, "合并文件失败。");
        }

        //检查文件md5
        File file = downloadFileFromMinIO(bucket_video, objectName);
        if(file == null){
            log.debug("下载合并后文件失败,mergeFilePath:{}",objectName);
            return R.error(false, "下载合并后文件失败。");
        }
        long size = file.length();
        media.setMsize(String.valueOf(size));
        String merge_md5 = getFileMd5(file);
        if (!merge_md5.equals(fileMd5)){
            return R.error(false, "文件合并校验失败，最终上传失败。");
        }
        String mimeType = getMimeType(extension);
        //文件入库
        Media mediaFiles = mediaService.saveToDb(fileMd5, media, bucket_video, objectName,mimeType);
        if (mediaFiles==null){
            return R.error(false,"文件入库失败");
        }

        //清除分块
        clearChunkFiles(fileRootPath,chunkTotal);

        return R.success(true);
    }

    private void clearChunkFiles(String chunkFileFolderPath,int chunkTotal){

        try {
            List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i)
                    .limit(chunkTotal)
                    .map(i -> new DeleteObject(chunkFileFolderPath.concat(Integer.toString(i))))
                    .collect(Collectors.toList());

            RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(bucket_video).objects(deleteObjects).build();
            Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
            results.forEach(r->{
                DeleteError deleteError = null;
                try {
                    deleteError = r.get();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String getFilePathByMd5(String fileMd5,String extension) {
        String filePath = fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + '/'+fileMd5+extension ;
        return filePath;
    }

    public File downloadFileFromMinIO(String bucket,String objectName){
        //临时文件
        File minioFile = null;
        FileOutputStream outputStream = null;
        try{
            InputStream stream = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .build());
            //创建临时文件
            minioFile=File.createTempFile("minio", ".merge");
            outputStream = new FileOutputStream(minioFile);
            IOUtils.copy(stream,outputStream);
            return minioFile;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }


    public boolean savaToMinio(String bucket,String localFilePath, String objectName,String mimeType){
        try {
            UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket(bucket)//文件存放的桶的名称
                    .object(objectName)//文件在桶里面的名称
                    .filename(localFilePath)//文件的存放路径
                    .contentType(mimeType)
                    .build();
            minioClient.uploadObject(uploadObjectArgs);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            WardrobeException.cast("文件上传失败");
        }
        return false;
    }

    @Transactional
    public Media saveToDb(String fileMd5,Media media, String bucket, String objectName,String mimeType ){
        Media mediaFiles = mediaMapper.selectById(fileMd5);
        if (mediaFiles==null){
            mediaFiles= new Media();
            BeanUtils.copyProperties(media,mediaFiles);
            mediaFiles.setMno(fileMd5);
            mediaFiles.setMbucket(bucket);
            mediaFiles.setMurl("/"+bucket+"/"+objectName);
            int insert = mediaMapper.insert(mediaFiles);
            if (insert < 0) {
                log.error("保存文件信息到数据库失败,{}",mediaFiles.toString());
                WardrobeException.cast("保存文件信息失败");
            }
            if (mimeType.equals("video/x-msvideo")){
                Boolean b = transform(mediaFiles);
                if (!b){
                    WardrobeException.cast("视频转码失败");
                }
            }
            log.debug("保存文件信息到数据库成功,{}",mediaFiles.toString());
        }
        return mediaFiles;
    }

    //小文件的存储命名方式
    private String getDefaultFolderPath(){
        SimpleDateFormat ft = new SimpleDateFormat("yyyy/MM/dd");
        String date = ft.format(new Date());
        return date+"/";
    }

    private String getFileRootPath(String fileMd5) {
        String filePath = fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk/" ;
        return filePath;
    }


    //获取文件的md5值
    private String getFileMd5(File file){
        try {
            String md5Hex = DigestUtils.md5Hex(new FileInputStream(file));
            return md5Hex;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    private String getMimeType(String extension){
        if(extension==null)
            extension = "";
        //根据扩展名取出mimeType
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
        //通用mimeType，字节流
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
        if(extensionMatch!=null){
            mimeType = extensionMatch.getMimeType();
        }
        return mimeType;
    }

    private Boolean transform(Media mediaProcess) {
        String bucket = mediaProcess.getMbucket();
        String objectName = mediaProcess.getMurl().substring(bucket.length() + 2);
        String fileId = mediaProcess.getMno();
        String filename = mediaProcess.getMname();

        File originalFile = mediaService.downloadFileFromMinIO(bucket, objectName);
        if (originalFile == null) {
            return false;
        }
        File tempFile = null;
        try {
            tempFile = File.createTempFile("minio", ".mp4");
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        //ffmpeg的安装位置
        String ffmpeg_path = ffmpegpath;
        //源avi视频的路径
        String video_path = originalFile.getAbsolutePath();
        //转换后mp4文件的名称
        String mp4_name = fileId + ".mp4";
        //转换后mp4文件的路径
        String mp4_path = tempFile.getAbsolutePath();
        //创建工具类对象
        Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpeg_path, video_path, mp4_name, mp4_path);
        //开始视频转换，成功将返回success
        String result = videoUtil.generateMp4();
        if (!result.equals("success")) {
            return false;
        }
        String newObjectName = mediaService.getFilePathByMd5(fileId, ".mp4");
        String url = "/" + bucket + "/" + newObjectName;
        boolean b = mediaService.savaToMinio(bucket, tempFile.getAbsolutePath(), newObjectName, "video/mp4");
        if (!b) {
            return false;
        }
        removeMinio(bucket,objectName);
        mediaProcess.setMurl(url);
        mediaProcess.setMname(mp4_name);
        mediaService.updateById(mediaProcess);
        return true;
    }

    private void removeMinio(String bucket,String objectName) {
        RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder()
                .bucket(bucket)
                .object(objectName)
                .build();
        try {
            minioClient.removeObject(removeObjectArgs);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
